iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0
Modern Web

從 Web 到宇宙:Three.js × WebGL 互動式太陽系與光影實作系列 第 25 篇

Day 25 - Animation Controls:Play、Pause × Speed

  • 分享至 

  • xImage
  •  

今天要繼續讓控制面板真正影響時間
也就是加入:

▶ Play
⏸ Pause
⏩ Speed

讓使用者可以暫停整個太陽系,也可以自由調整行星運行速度。

由於目前專案程式碼已逐漸增加,從 Day 23 開始文章只列出當日新增或修改的程式碼。
完整整合版將於 Day 30 最終完成時提供。

一、今天完成目標

今天會完成:

  • 新增 Play / Pause 按鈕
  • 暫停與恢復所有天體動畫
  • 新增動畫速度 Slider
  • 即時顯示目前速度
  • 建立 isPlaying
  • 建立 timeScale
  • 將 delta 與速度倍率結合
  • 保留 OrbitControls,在暫停時仍能旋轉 Camera

今天完成後控制面板會變成:

Solar System Controls

☑ 顯示軌道
☑ 金星大氣層
☑ 星空背景

────────────────

⏸ Pause

Animation Speed
──────●──────

1.0x

二、本日修改摘要

項目 修改內容
HTML 新增 Pause 按鈕
HTML 新增 Speed Slider
CSS 新增按鈕與 Slider 樣式
JavaScript 新增 isPlaying
JavaScript 新增 timeScale
Animation 使用速度倍率控制 delta

三、新增 Play / Pause 按鈕

首先在 Day 24 的控制面板下面加入:

<hr>

<button id="playPauseButton">
    ⏸ Pause
</button>

一開始動畫本來就在運行。
所以按鈕初始顯示:⏸ Pause
代表目前正在播放,如果按下去可以暫停

四、建立 isPlaying

JavaScript 新增:

let isPlaying =
    true;

這個變數用來記錄:

true → 動畫運行
false → 動畫暫停

接著取得按鈕:

const playPauseButton =
    document.getElementById(
        "playPauseButton"
    );

五、切換 Play / Pause

加入:

playPauseButton.addEventListener(
    "click",
    () => {

        isPlaying =
            !isPlaying;


        if (
            isPlaying
        ) {

            playPauseButton.textContent =
                "⏸ Pause";

        } else {

            playPauseButton.textContent =
                "▶ Play";

        }

    }
);

這裡最重要的是:

isPlaying =
    !isPlaying;

假設原本 true 經過 !true 就變成 false
再按一次 !false 又變回 true
所以可以一直 Play , Pause 切換。

六、Animation 要怎麼暫停?

不能直接停止:

requestAnimationFrame(
    animate
);

因為如果完全停止 Animation Loop:
OrbitControls、Renderer、其他互動也可能一起停止更新。
所以比較適合的方法是:
Animation Loop → 繼續執行 但 天體位置 → 不更新
也就是把行星動畫包進:

if (
    isPlaying
) {

    ...

}

七、新增 Animation Speed,Slider的設定

接下來加入速度控制。HTML:

<div class="speed-control">

    <label for="speedSlider">
        Animation Speed
    </label>

    <input
        type="range"
        id="speedSlider"
        min="0.1"
        max="5"
        step="0.1"
        value="1"
    >

    <span id="speedValue">
        1.0x
    </span>

</div>

這次第一次使用 type="range" 也就是 Slider。

Slider,我們設定:

min="0.1"
max="5"
step="0.1"
value="1"

分別代表:

min → 最慢 0.1x
max → 最快 5x
step → 每次改變 0.1
value → 初始速度 1x

八、建立 timeScale,讀取 Slider

JavaScript:

let timeScale =
    1;

1 代表正常速度

取得 Slider:

const speedSlider =
    document.getElementById(
        "speedSlider"
    );

const speedValue =
    document.getElementById(
        "speedValue"
    );

讀取 Slider 加入:

speedSlider.addEventListener(
    "input",
    () => {

        timeScale =
            Number(
                speedSlider.value
            );


        speedValue.textContent =
            timeScale.toFixed(1) +
            "x";

    }
);

這次使用 input 而不是 change。
因為 Slider 移動時,我們希望拖動就立刻更新,
而不是等放開 Slider 才改變。

九、為什麼需要 Number?

HTML Input 的 speedSlider.value
拿到的其實是 String 像是 "2.5"
所以我們使用:

Number(
    speedSlider.value
);

把它轉成 2.5 方便後面做數學運算。

十、把 Speed 套到 delta

我們原本:

const delta =
    clock.getDelta();

現在加入:

const scaledDelta =
    delta *
    timeScale;

假設 delta = 0.016
當 timeScale = 1
就是 0.016 × 1 = 0.016
正常速度。

如果 timeScale = 2
則 0.016 × 2 = 0.032

所有動畫變快兩倍。
如果 timeScale = 0.5
就是 0.016 × 0.5 = 0.008
變成一半速度。

十一、修改 Sun Rotation,Planet Animation

原本 Sun Rotation:

sun.rotation.y +=
    0.5 *
    delta;

改成:

sun.rotation.y +=
    0.5 *
    scaledDelta;

原本 Planet Animation:

planet.mesh.rotation.y +=
    planet.rotationSpeed *
    delta;

改成:

planet.mesh.rotation.y +=
    planet.rotationSpeed *
    scaledDelta;

公轉也改成:

planet.angle +=
    planet.orbitSpeed *
    scaledDelta;

這樣 Slider 就會同時影響 Planet Self Rotation + Planet Revolution

十二、Venus Atmosphere

原本:

venusAtmosphere.rotation.y +=
    0.15 *
    delta;

改成:

venusAtmosphere.rotation.y +=
    0.15 *
    scaledDelta;

因此金星大氣也會跟著速度一起改變。

十三、Moon

Moon 也要使用 scaledDelta
公轉:

moonAngle +=
    moonOrbitSpeed *
    scaledDelta;

自轉:

moon.rotation.y +=
    0.4 *
    scaledDelta;

因此我們的 Speed Slider 現在會影響:

Sun Rotation
Planet Rotation
Planet Revolution
Venus Atmosphere
Moon Rotation
Moon Revolution

也就是整個太陽系的時間倍率。

十四、Animation 最重要的修改

今天 Animation 的核心結構變成:

function animate() {

    requestAnimationFrame(
        animate
    );


    const delta =
        clock.getDelta();


    const scaledDelta =
        delta *
        timeScale;


    if (
        isPlaying
    ) {

        // Sun

        // Planets

        // Venus Atmosphere

        // Moon

    }


    controls.update();


    renderer.render(
        scene,
        camera
    );

}

這就是今天最重要的邏輯。

十五、加入新的 CSS

控制面板可以再加入:

.control-panel hr {
    margin: 18px 0;

    border: none;

    border-top:
        1px solid
        rgba(
            255,
            255,
            255,
            0.15
        );
}


.control-panel button {
    width: 100%;

    padding: 10px;

    border: none;

    border-radius: 8px;

    background:
        rgba(
            255,
            255,
            255,
            0.12
        );

    color: white;

    font-size: 14px;

    cursor: pointer;
}


.control-panel button:hover {
    background:
        rgba(
            255,
            255,
            255,
            0.2
        );
}


.speed-control {
    margin-top: 18px;
}


.speed-control label {
    margin-bottom: 8px;
}


.speed-control input {
    width: 100%;

    margin: 8px 0;
}


#speedValue {
    display: block;

    text-align: right;

    color: #aaaaaa;

    font-size: 13px;
}

十六、今天小結

今天控制的是 動畫是否運行 + 動畫運行速度
現在 UI 已經可以做到:

Solar System Controls

☑ 顯示軌道
☑ 金星大氣層
☑ 星空背景

⏸ Pause

Animation Speed
0.1x ─────●───── 5.0x

其中最重要的兩個變數:

let isPlaying =
    true;

let timeScale =
    1;

分別負責:
isPlaying → 播放 / 暫停
timeScale → 時間倍率

再搭配:

const scaledDelta =
    delta *
    timeScale;

我們就可以讓整個太陽系使用同一套時間速度。

Day 26 預告

「Raycaster:用滑鼠選擇行星」

目前我們的控制方式主要集中在右上角 UI
Day 26 要開始直接操作 3D Scene
我們會加入 THREE.Raycaster
讓使用者直接滑鼠點擊哪一顆行星 則被選中的行星:對應 Orbit 會改變顏色。
也就是從 Day 26 開始,我們會真正做到滑鼠和3D物件的互動。


上一篇
Day 24 - UI Controls:替太陽系加入控制面板
下一篇
Day 26 - Raycaster:用滑鼠選擇行星
系列文
從 Web 到宇宙:Three.js × WebGL 互動式太陽系與光影實作 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言